/*
 * IconView.java
 *
 * Created on December 14, 2002, 2:36 PM
 */

package fr.umlv.cmis.listview;

import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.GraphicsEnvironment;
import java.awt.HeadlessException;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.List;

import javax.swing.JComponent;
import javax.swing.ListSelectionModel;
import javax.swing.Scrollable;
import javax.swing.TransferHandler;
import javax.swing.UIManager;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ListDataEvent;
import javax.swing.event.ListDataListener;
import javax.swing.event.ListSelectionEvent;
import javax.swing.filechooser.FileSystemView;

import kiyut.swing.shell.image.ImageUtilities;

import org.apache.chemistry.opencmis.client.api.CmisObject;
import org.apache.chemistry.opencmis.client.api.Document;
import org.apache.chemistry.opencmis.commons.enums.BaseTypeId;

import fr.umlv.dnd.CmisObjectTransferHandler;
import fr.umlv.dnd.CustomDragGestureRecognizer;
import fr.umlv.dnd.CustomDropTargetListener;

// TODO: Auto-generated Javadoc
/**
 * The Class CmisIconView.
 */
public class CmisIconView extends JComponent implements Scrollable, ViewComponent {
	
	/** The Constant serialVersionUID. */
	private static final long serialVersionUID = 1L;


	/** The Constant ICON_SIZE. */
	private static final int ICON_SIZE = 22;
	
	
    /** The property change listener. */
    private PropertyChangeListener propertyChangeListener;
    
    /** The flow layout. */
    private FlowLayout flowLayout;
    
    /** The column count. */
    private int columnCount = -1;
    
    /** The selection model. */
    protected ListSelectionModel selectionModel;
    
    /** The data model. */
    protected final CmisObjectListViewModel dataModel;
    
    /** The cells. */
    protected List<IconCell> cells;
    
    /** The cell editor. */
    protected IconCellEditor cellEditor;
    
    /** The editing index. */
    protected int editingIndex = -1;
    
    /** The selection background. */
    protected Color selectionBackground;
    
    /** The selection foreground. */
    protected Color selectionForeground;
    
    /** The drag enabled. */
    protected boolean dragEnabled = false;
    
    /**
     * Instantiates a new cmis icon view.
     *
     * @param dataModel the data model
     */
    public CmisIconView(CmisObjectListViewModel dataModel) {
    	
        cells = new ArrayList<IconCell>();
        
        cellEditor = new IconCellEditor();
        cellEditor.addCellEditorListener(new javax.swing.event.CellEditorListener() {
            public void editingCanceled(ChangeEvent evt) { CmisIconView.this.editingCanceled(evt); }
            public void editingStopped(ChangeEvent evt) { CmisIconView.this.editingStopped(evt); }
        });
        
        this.dataModel = dataModel;
        dataModel.addListDataListener(new ListDataListener() {
            public void contentsChanged(ListDataEvent evt) { onContentsChanged(evt); }
            public void intervalAdded(ListDataEvent evt) { onIntervalAdded(evt); }
            public void intervalRemoved(ListDataEvent evt) { onIntervalRemoved(evt); }
        });
        
        addComponentListener(new ComponentAdapter() {
            public void componentResized(ComponentEvent evt) { calculatePreferredSize(); }
        });
        
        // DnD Support
        
        setTransferHandler(new CmisObjectTransferHandler());
        IconViewDragGestureRecognizer dragRecognizer = new IconViewDragGestureRecognizer();
        addMouseListener(dragRecognizer);
        addMouseMotionListener(dragRecognizer);
        addMouseListener(new MouseAdapter() {
            public void mouseClicked(MouseEvent evt) { onMouseClicked(evt); }
        });
        
        addKeyListener(new KeyAdapter() {
            public void keyPressed(KeyEvent evt) { onKeyPressed(evt); }
        });
        
        propertyChangeListener = new PropertyChangeHandler();
        addPropertyChangeListener(propertyChangeListener);
        
        flowLayout = new FlowLayout();
        flowLayout.setAlignment(FlowLayout.LEFT);
        setPreferredSize(new Dimension(128, 128));
        setLayout(flowLayout);
        //setOpaque(true);
        setBackground(UIManager.getColor("List.background"));
        setForeground(UIManager.getColor("List.foreground"));
        setFont(UIManager.getFont("Table.font"));
        setSelectionBackground(UIManager.getColor("List.selectionBackground"));
        setSelectionForeground(UIManager.getColor("List.selectionForeground"));
    }
    
    /* (non-Javadoc)
     * @see javax.swing.JComponent#setTransferHandler(javax.swing.TransferHandler)
     */
    @Override
    public void setTransferHandler(TransferHandler newHandler) {
        super.setTransferHandler(newHandler);
        new IconViewDropTargetListener(this);
    }
    
    /**
     * Gets the selection background.
     *
     * @return the selection background
     */
    public Color getSelectionBackground() {
        return selectionBackground;
    }
    
    
    /**
     * Sets the selection background.
     *
     * @param selectionBackground the new selection background
     */
    public void setSelectionBackground(Color selectionBackground) {
        Color oldValue = this.selectionBackground;
        this.selectionBackground = selectionBackground;
        firePropertyChange("selectionBackground", oldValue, selectionBackground);
    }
    
    /**
     * Gets the selection foreground.
     *
     * @return the selection foreground
     */
    public Color getSelectionForeground() {
        return selectionForeground;
    }
    
    
    /**
     * Sets the selection foreground.
     *
     * @param selectionForeground the new selection foreground
     */
    public void setSelectionForeground(Color selectionForeground) {
        Color oldValue = this.selectionForeground;
        this.selectionForeground = selectionForeground;
        firePropertyChange("selectionForeground", oldValue, selectionForeground);
    }
    
    /**
     * Sets the drag enabled.
     *
     * @param b the new drag enabled
     */
    public void setDragEnabled(boolean b) {
        if (b && GraphicsEnvironment.isHeadless()) {
            throw new HeadlessException();
        }
        dragEnabled = b;
    }
    
    /**
     * Gets the drag enabled.
     *
     * @return the drag enabled
     */
    public boolean getDragEnabled() {
        return dragEnabled;
    }
    
    /**
     * Refresh.
     */
    protected void refresh() {
        this.removeAll();
        this.repaint();
        selectionModel.clearSelection();
        cells.clear();
        
        FileSystemView fsv = dataModel.getFileSystemView();
        
        for (int i=0; i < dataModel.getSize(); i++) {
            CmisObject file = (CmisObject)dataModel.getFile(i);
            IconCell cell = createCell(file,fsv);
            
            cells.add(cell);
            this.add(cell);
        }
        
        // set preferredSize
        calculatePreferredSize();
        revalidate();
        repaint();
    }
    
    /**
     * Creates the cell.
     *
     * @param file the file
     * @param fsv the fsv
     * @return the icon cell
     */
    protected IconCell createCell(CmisObject file,FileSystemView fsv) {
        IconCell cell = new IconCell(this);
        updateCell(cell,file,fsv);
        return cell;
    }
    
    /**
     * Update cell.
     *
     * @param cell the cell
     * @param file the file
     * @param fsv the fsv
     */
    protected void updateCell(IconCell cell,CmisObject file,FileSystemView fsv) {
    	cell.setText(file.getName());
        cell.setImageRescale(1.5, 1.5);
        
        if (BaseTypeId.CMIS_FOLDER.equals(file.getBaseTypeId())) {
        	cell.setImage(ImageUtilities.iconToBufferedImage(IconFactory.getFolderIcon(this)));
        	
        }else {
        	cell.setImage(ImageUtilities.iconToBufferedImage(IconFactory.getIcon(((Document)file).getContentStreamMimeType(),this)));   
        }
    }
    
    /* (non-Javadoc)
     * @see fr.umlv.cmis.listview.ViewComponent#getViewModel()
     */
    public CmisObjectListViewModel getViewModel() {
        return dataModel;
    }
    
    /**
     * Sets the selection model.
     *
     * @param selectionModel the new selection model
     */
    public void setSelectionModel(ListSelectionModel selectionModel) {
        if (selectionModel == null) {
            throw new IllegalArgumentException("Cannot set a null SelectionModel");
        }
        
        this.selectionModel = selectionModel;
        this.selectionModel.addListSelectionListener(new javax.swing.event.ListSelectionListener() {
            public void valueChanged(ListSelectionEvent e) {
                onListSelectionChanged(e);
            }
        });
    }
    
    /* (non-Javadoc)
     * @see fr.umlv.cmis.listview.ViewComponent#getSelectionModel()
     */
    public ListSelectionModel getSelectionModel() {
        return selectionModel;
    }
    
    /**
     * Checks if is selected index.
     *
     * @param index the index
     * @return true, if is selected index
     */
    public boolean isSelectedIndex(int index) {
        return getSelectionModel().isSelectedIndex(index);
    }
    
    /**
     * Calculate preferred size.
     */
    private void calculatePreferredSize() {
        if ( (cells.size() > 0) && (this.isVisible()) ) {
            IconCell cell = cells.get(0);
            double width = getVisibleRect().getWidth();
            columnCount = (int)(width / (cell.getPreferredSize().getWidth() + flowLayout.getVgap()));
            if (columnCount > 0) {
                int row = (int)(cells.size()/columnCount) + 1;
                double height = row * (cell.getPreferredSize().getHeight() + flowLayout.getHgap());
                Dimension dim = new Dimension((int)width,(int)height);
                setPreferredSize(dim);
            }
        }
    }
    
    /**
     * Checks if is editing.
     *
     * @return true, if is editing
     */
    public boolean isEditing() {
        boolean b = false;
        CmisObjectListViewModel dataModel = getViewModel();
        if (dataModel.getState() == CmisObjectListViewModel.EDIT) { b = true; }
        return b;
        
    }
    
    /**
     * Gets the cell editor.
     *
     * @return the cell editor
     */
    public IconCellEditor getCellEditor() {
        return cellEditor;
    }
    
    /**
     * Sets the editing index.
     *
     * @param index the new editing index
     */
    private void setEditingIndex(int index) {
        editingIndex = index;
    }
    
    /**
     * Gets the editing index.
     *
     * @return the editing index
     */
    public int getEditingIndex() {
        return editingIndex;
    }
    
    /**
     * Edits the cell at.
     *
     * @param index the index
     * @return true, if successful
     */
    public boolean editCellAt(int index) {
        boolean b = false;
        
        if ( (index < 0) || (index >= dataModel.getRowCount()) ) {
            throw new IndexOutOfBoundsException("invalid index");
        }
        
        selectionModel.clearSelection();
        selectionModel.setSelectionInterval(index,index);
        setEditingIndex(index);
        
        CmisObjectListViewModel dataModel = getViewModel();
        Object value = dataModel.getFile(index);
        JComponent editorComp = cellEditor.getIconCellEditorComponent(this, value, true, index);
        IconCell cell = cells.get(index);
        cell.setCellEditor(editorComp);
        cell.startCellEditing();
        cell.validate();
        cell.repaint();
        
        b = true;
        
        return b;
    }
    
    /**
     * Removes the editor.
     */
    protected void removeEditor() {
        if ((editingIndex>=0) && (editingIndex<cells.size())) {
            IconCell cell = cells.get(editingIndex);
            cell.stopCellEditing();
            cell.removeCellEditor();
            editingIndex = -1;
            cell.validate();
        }
    }
    
    /**
     * Editing canceled.
     *
     * @param evt the evt
     */
    public void editingCanceled(ChangeEvent evt) {
    	CmisObjectListViewModel dataModel = getViewModel();
        dataModel.setState(CmisObjectListViewModel.BROWSE);
        removeEditor();
    }
    
    /**
     * Editing stopped.
     *
     * @param evt the evt
     */
    public void editingStopped(ChangeEvent evt) {
    	CmisObjectListViewModel dataModel = getViewModel();
        dataModel.setState(CmisObjectListViewModel.BROWSE);
        removeEditor();
    }
    
    /* (non-Javadoc)
     * @see javax.swing.Scrollable#getPreferredScrollableViewportSize()
     */
    public Dimension getPreferredScrollableViewportSize() {
        return getPreferredSize();
    }
    
    /* (non-Javadoc)
     * @see javax.swing.Scrollable#getScrollableBlockIncrement(java.awt.Rectangle, int, int)
     */
    public int getScrollableBlockIncrement(Rectangle visibleRect, int orientation, int direction) {
        int i = 16;
        return i;
    }
    
    /* (non-Javadoc)
     * @see javax.swing.Scrollable#getScrollableTracksViewportHeight()
     */
    public boolean getScrollableTracksViewportHeight() {
        return false;
    }
    
    /* (non-Javadoc)
     * @see javax.swing.Scrollable#getScrollableTracksViewportWidth()
     */
    public boolean getScrollableTracksViewportWidth() {
        return true;
    }
    
    /* (non-Javadoc)
     * @see javax.swing.Scrollable#getScrollableUnitIncrement(java.awt.Rectangle, int, int)
     */
    public int getScrollableUnitIncrement(Rectangle visibleRect, int orientation, int direction) {
        return getScrollableBlockIncrement(visibleRect,orientation,direction);
    }
    
    
    //////////////////////////
    // event handling
    /////////////////////////
    
    
    /**
     * On mouse clicked.
     *
     * @param evt the evt
     */
    private void onMouseClicked(MouseEvent evt) {
        requestFocusInWindow();
        
        if (evt.getClickCount() > 1) {
            return;
        }
        
        Component cell = getComponentAt(evt.getX(),evt.getY());
        
        if ( (evt.getClickCount() == 1) && (cell != null) ) {
            int index = cells.indexOf(cell);
            if (evt.isShiftDown()) {
                selectionModel.addSelectionInterval(selectionModel.getLeadSelectionIndex(),index);
            } else if (evt.isControlDown()) {
                selectionModel.addSelectionInterval(index,index);
            } else {
                //selectionModel.clearSelection();
                selectionModel.setSelectionInterval(index, index);
            }
        }
    }
    
    /**
     * On key pressed.
     *
     * @param evt the evt
     */
    private void onKeyPressed(KeyEvent evt) {
    	CmisObjectListViewModel dataModel = getViewModel();
        if (dataModel.getState()!=CmisObjectListViewModel.BROWSE) { return; }
        
        int index = getSelectionModel().getMaxSelectionIndex();
        int keyCode = evt.getKeyCode();
        if (keyCode==KeyEvent.VK_RIGHT) {
            index = index + 1;
        } else if (keyCode==KeyEvent.VK_LEFT) {
            index = index - 1;
        } else if (keyCode==KeyEvent.VK_UP) {
            index = index - columnCount;
        } else if (keyCode==KeyEvent.VK_DOWN) {
            index = index + columnCount;
        } else {
            return;
        }
        
        if ((index<0) || (index >= cells.size())) {
            return;
        }
        
        IconCell cell = cells.get(index);
        MouseEvent mouseEvent = new MouseEvent(this,MouseEvent.MOUSE_CLICKED,
                evt.getWhen(),evt.getModifiers(),cell.getX(),cell.getY(),1,false,MouseEvent.BUTTON1);
        
        onMouseClicked(mouseEvent);
    }
    
    /**
     * On contents changed.
     *
     * @param evt the evt
     */
    private void onContentsChanged(ListDataEvent evt) {
        // changeAll
        if ((evt.getIndex0()==CmisObjectListViewModel.ALL_INDEX) &&  (evt.getIndex1()==CmisObjectListViewModel.ALL_INDEX) ){
            refresh();
            return;
        }
        
        FileSystemView fsv = dataModel.getFileSystemView();
        for(int i=evt.getIndex0(); i<=evt.getIndex1(); i++) {
        	CmisObject file = dataModel.getFile(i);
            IconCell cell = cells.get(i);
            updateCell(cell,file,fsv);
            cell.repaint();
        }
    }
    
    /**
     * On interval added.
     *
     * @param evt the evt
     */
    private void onIntervalAdded(ListDataEvent evt) {
        FileSystemView fsv = dataModel.getFileSystemView();
        for(int i=evt.getIndex0(); i<=evt.getIndex1(); i++) {
            CmisObject file = (CmisObject)dataModel.getFile(i);
            IconCell cell = createCell(file,fsv);
            this.add(cell,i);
            cells.add(i, cell);
        }
        calculatePreferredSize();
        revalidate();
        repaint();
    }
    
    /**
     * On interval removed.
     *
     * @param evt the evt
     */
    private void onIntervalRemoved(ListDataEvent evt) {
        for(int i=evt.getIndex0(); i<=evt.getIndex1(); i++) {
            IconCell cell = cells.remove(i);
            this.remove(cell);
        }
        calculatePreferredSize();
        revalidate();
        repaint();
    }
    
    /**
     * On list selection changed.
     *
     * @param evt the evt
     */
    private void onListSelectionChanged(ListSelectionEvent evt) {
        cellEditor.cancelCellEditing();
        
        if (!selectionModel.isSelectionEmpty()) {
            Component comp = (Component)cells.get(selectionModel.getMaxSelectionIndex());
            scrollRectToVisible(comp.getBounds());
        }
        
        for (int i=0; i<cells.size(); i++) {
            IconCell cell = cells.get(i);
            if (selectionModel.isSelectedIndex(i)) {
                cell.setCellHasFocus(true);
            } else {
                cell.setCellHasFocus(false);
            }
            cell.repaint();
        }
    }
    
    /**
     * The Class PropertyChangeHandler.
     */
    protected class PropertyChangeHandler implements PropertyChangeListener {
        
        /* (non-Javadoc)
         * @see java.beans.PropertyChangeListener#propertyChange(java.beans.PropertyChangeEvent)
         */
        public void propertyChange(PropertyChangeEvent evt) {
            String propertyName = evt.getPropertyName();
            
            if (propertyName.equals("selectionBackground") || propertyName.equals("selectionForeground") ) {
                repaint();
            }
        }
    }
    
    /**
     * The Class IconViewDragGestureRecognizer.
     */
    protected class IconViewDragGestureRecognizer extends CustomDragGestureRecognizer {
        
        /* (non-Javadoc)
         * @see fr.umlv.dnd.CustomDragGestureRecognizer#isDragPossible(java.awt.event.MouseEvent)
         */
    	@Override
        protected boolean isDragPossible(MouseEvent evt) {
            if (super.isDragPossible(evt)) {
                CmisIconView comp = (CmisIconView)evt.getSource();
                if (comp.getDragEnabled()) {
                    Component cell = getComponentAt(evt.getX(),evt.getY());
                    int index = cells.indexOf(cell);
                    if ((index != -1) && comp.isSelectedIndex(index)) {
                        return true;
                    }
                }
            }
            return false;
        }
    }
    
    /**
     * The listener interface for receiving iconViewDropTarget events.
     * The class that is interested in processing a iconViewDropTarget
     * event implements this interface, and the object created
     * with that class is registered with a component using the
     * component's <code>addIconViewDropTargetListener<code> method. When
     * the iconViewDropTarget event occurs, that object's appropriate
     * method is invoked.
     *
     * @see IconViewDropTargetEvent
     */
    protected class IconViewDropTargetListener extends CustomDropTargetListener {
        
        /** The selected indices. */
        private int[] selectedIndices;
        
        
        /**
         * Instantiates a new icon view drop target listener.
         *
         * @param j the j
         */
        public IconViewDropTargetListener(JComponent j) {
        	super(j);
		}
        
        /* (non-Javadoc)
         * @see fr.umlv.dnd.CustomDropTargetListener#saveComponentState(javax.swing.JComponent)
         */
        @Override
        protected void saveComponentState(JComponent comp) {
            CmisIconView iconView = (CmisIconView)comp;
            CmisObjectListViewSelectionModel sm = (CmisObjectListViewSelectionModel)iconView.getSelectionModel();
            selectedIndices = sm.getSelectedIndices();
        }
        
        /* (non-Javadoc)
         * @see fr.umlv.dnd.CustomDropTargetListener#restoreComponentState(javax.swing.JComponent)
         */
        @Override
        protected void restoreComponentState(JComponent comp) {
            CmisIconView iconView = (CmisIconView)comp;
            CmisObjectListViewSelectionModel sm = (CmisObjectListViewSelectionModel)iconView.getSelectionModel();
            sm.setSelectedIndices(selectedIndices);
        }
        
        /* (non-Javadoc)
         * @see fr.umlv.dnd.CustomDropTargetListener#updateInsertionLocation(javax.swing.JComponent, java.awt.Point)
         */
        @Override
        protected void updateInsertionLocation(JComponent comp, Point p) {
            CmisIconView iconView = (CmisIconView)comp;
            Component cell = getComponentAt((int)p.getX(),(int)p.getY());
            int index = cells.indexOf(cell);
            if (index != -1) {
                iconView.getSelectionModel().setSelectionInterval(index, index);
            }
        }
        
    }

	/* (non-Javadoc)
	 * @see fr.umlv.cmis.listview.ViewComponent#getIconSize()
	 */
	@Override
	public int getIconSize() {
		return ICON_SIZE;
	}
}

